﻿// ----------------------------------------------------------------------------------
// <copyright file="ContextContainer.cs" company="Christopher Hines">
// Copyright (c) 2012 All Right Reserved
// </copyright>
// <author>Chris Hines</author>
// <date>2012-02-27</date>
// <summary>
//     Summary goes here
// </summary>
// ----------------------------------------------------------------------------------

namespace Copperfield
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Support class for static configuration of the framework
    /// </summary>
    internal static class ContextContainer
    {
        /// <summary>
        /// Gets the resolution delegate
        /// </summary>
        /// <value>
        /// The resolution delegate
        /// </value>
        private static readonly Func<Type, object> resolutionDelegate = CopperfieldConfiguration.ResolutionDelegate;

        /// <summary>
        /// Gets an instance of a given plugin type using a specified context type
        /// </summary>
        /// <param name="pluginType">The plugin type</param>
        /// <param name="contextType">The context type</param>
        /// <returns>An instance of the plugin type</returns>
        internal static object GetInstance(Type pluginType, Type contextType)
        {
            HelperUtility.AssertIsIComparable(contextType, string.Concat(contextType.Name, " must implement IComparable."));
            HelperUtility.AssertHasPublicParameterlessConstructor(contextType, string.Concat(contextType.Name, " does not have a public, parameterless constructor."));

            var contextManagerType = TypeUtility.GetTypeOfContextManager(contextType);
            var contextManager = resolutionDelegate(contextManagerType) as IContextManager;
            var fallbackType = ContextConfigurationFactory.GetInstance(pluginType, ContextConfigurationFactory.DefaultInstanceKey);
            var context = contextManager != null ? contextManager.Context : null;

            if (context == null)
            {
                return GetFallbackInstance(pluginType, fallbackType);
            }

            var kvp = PluggableTypeCache.InstanceDictionaryFor(pluginType).FirstOrDefault(x => x.Key.CompareTo(context) == 0);

            if (kvp.Value == null)
            {
                return GetFallbackInstance(pluginType, fallbackType);
            }

            var resolvedInstance = resolutionDelegate(kvp.Value);

            if (resolvedInstance != null && pluginType.IsInstanceOfType(resolvedInstance))
            {
                return resolvedInstance;
            }

            return GetFallbackInstance(pluginType, fallbackType);
        }

        /// <summary>
        /// Returns all instances of a plugin type that match the current context
        /// </summary>
        /// <param name="pluginType">
        /// The plugin type.
        /// </param>
        /// <param name="contextType">
        /// The context type.
        /// </param>
        /// <returns>
        /// The instances of a plugin type that match the current context.
        /// </returns>
        internal static object GetInstances(Type pluginType, Type contextType)
        {
            var pluginGenericArgument = pluginType.GetGenericArguments()[0];

            HelperUtility.AssertIsIComparable(
                contextType, string.Concat(contextType.Name, " must implement IComparable."));
            
            HelperUtility.AssertHasPublicParameterlessConstructor(
                contextType, string.Concat(contextType.Name, " does not have a public, parameterless constructor."));

            var contextManagerType = TypeUtility.GetTypeOfContextManager(contextType);
            var contextManager = resolutionDelegate(contextManagerType) as IContextManager;
            var fallbackType = ContextConfigurationFactory.GetInstance(pluginGenericArgument, ContextConfigurationFactory.DefaultInstanceKey);
            var context = contextManager != null ? contextManager.Context : null;

            if (context == null)
            {
                return new[] { GetFallbackInstance(pluginGenericArgument, fallbackType) };
            }

            var kvpCollection = PluggableTypeCache.InstanceDictionaryFor(pluginGenericArgument).Where(x => x.Key.CompareTo(context) == 0);

            var colA = kvpCollection.Select(x => x.Value);
            var colB = colA.Select(match => resolutionDelegate(match));
            var colC = colB.Where(instance => instance != null && pluginGenericArgument.IsInstanceOfType(instance));
            var list = colC.ToList();

            var assembledList = list.Count > 0 
                ? list
                : new List<object> { GetFallbackInstance(pluginGenericArgument, fallbackType) };

            return TypeUtility.GetCollectionFlattenerDelegate(pluginGenericArgument)(assembledList);
        }

        /// <summary>
        /// Returns a collection of a plugin type
        /// </summary>
        /// <typeparam name="TPlugin">
        /// The plugin type
        /// </typeparam>
        /// <typeparam name="TContext">
        /// The context type
        /// </typeparam>
        /// <returns>
        /// A collection of a plugin type
        /// </returns>
        internal static IContextualCollection<TPlugin> GetAllInstances<TPlugin, TContext>()
        {
            var instances = GetInstances(typeof(IEnumerable<TPlugin>), typeof(TContext)) as IContextualCollection<TPlugin>;
            var fallbackType = ContextConfigurationFactory.GetInstance(typeof(TPlugin), ContextConfigurationFactory.DefaultInstanceKey);

            if (instances != null)
            {
                return instances;
            }

            return new ContextualCollection<TPlugin> { GetFallbackInstance<TPlugin>(fallbackType) };
        }

        /// <summary>
        /// Returns an instance of a plugin type
        /// </summary>
        /// <typeparam name="TPlugin">The plugin type</typeparam>
        /// <typeparam name="TContext">The context type</typeparam>
        /// <returns>An instance of the plugin type</returns>
        internal static TPlugin GetInstance<TPlugin, TContext>() where TContext : IComparable, new()
        {
            var instance = GetInstance(typeof(TPlugin), typeof(TContext));
            
            var fallbackType = ContextConfigurationFactory.GetInstance(typeof(TPlugin), ContextConfigurationFactory.DefaultInstanceKey);

            if (!ReferenceEquals(instance, null) && instance is TPlugin)
            {
                return (TPlugin)instance;
            }

            return GetFallbackInstance<TPlugin>(fallbackType);
        }

        /// <summary>
        /// Returns an instance of a fallback type
        /// </summary>
        /// <typeparam name="TPlugin">The plugin type</typeparam>
        /// <param name="fallbackType">The fallback type</param>
        /// <returns>An instance of the fallback type</returns>
        private static TPlugin GetFallbackInstance<TPlugin>(Type fallbackType)
        {
            var instance = GetFallbackInstance(fallbackType, typeof(TPlugin));

            if (!(instance is TPlugin))
            {
                var message = string.Concat(
                    "An error occurred while trying to resolve an instance of ",
                    fallbackType.Name,
                    ": A default instance was found for ",
                    typeof(TPlugin).Name,
                    " but the default instance is not a type of ",
                    typeof(TPlugin).Name,
                    ".");

                throw new ArgumentException(message);
            }

            return (TPlugin)instance;
        }

        /// <summary>
        /// Returns an instance of the fallback type
        /// </summary>
        /// <param name="pluginType">The plugin type</param>
        /// <param name="fallbackType">The fallback type</param>
        /// <returns>An instance of the fallback type</returns>
        private static object GetFallbackInstance(Type pluginType, Type fallbackType)
        {
            if (fallbackType == null)
            {
                var message = string.Concat(
                    "An runtime exception occurred while attempting to resolve an instance of ",
                    pluginType.Name,
                    ": The framework was asked to provide a default instance, but no default instance has been defined");

                throw new ArgumentException(message);
            }

            var instance = resolutionDelegate(fallbackType);

            if (ReferenceEquals(instance, null))
            {
                var message = string.Concat(
                    "An runtime exception occurred while attempting to resolve an instance of ",
                    pluginType.Name,
                    ": The framework was asked to provide a default instance, but the default instance was null.");

                throw new ArgumentException(message);
            }

            return instance;
        }
    }
}
